﻿using System;
using System.IO;
using System.ComponentModel;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Configuration;
using System.Diagnostics;
using RFServer.Interactive;

namespace RFServer
{
    class Setup
    {
        public static IPAddress IP { get; set; }                        /* Не используется                      */
        public static string ServiceMainHost                            /* Базовый адрес службы-сервера         */
        {                                                               /* без порта                            */
            get { return "net.tcp://localhost"; }                       
            private set { }
        }
        public static string ServiceMainAddress                         /* Базовый адрес службы-сервера         */
        {                       
            get { return ServiceMainHost + ":" + Setup.Port + "/"; }
            private set { }
        }
        public static string ServiceLocalAddress                        /* Базовый адрес вспомогательной службы */
        {
            get { return "net.pipe://RFServerLocalService"; }
            /* Адрес обязан согласовываться с клиентскими */
            /* настройками в App.config                   */
            private set { }
        }
        public static int Port { get; set; }                            /* Порт, на котором запущена служба     */

        public static string WorkingDirectory                           /* %APPDATA%\RFServer                   */
        {
            get
            {

                string appdata = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);

                if (appdata[appdata.Length - 1] != '\\')
                    appdata += "\\RFServer\\";

                if (!Directory.Exists(appdata))
                    Directory.CreateDirectory(appdata);

                return appdata;

            }
        }                        

        public static List<IPAddress> GetIPv4Adresses()
        {
            String hostName = Dns.GetHostName();
            IPHostEntry ipHostEntry = Dns.GetHostEntry(hostName);

            List<IPAddress> ip4HostEntries = new List<IPAddress>();

            foreach (IPAddress ip4 in ipHostEntry.AddressList)
                if (ip4.AddressFamily == AddressFamily.InterNetwork)
                    ip4HostEntries.Add(ip4);

            return ip4HostEntries;
        }

        public static string DatabaseName { get; set; }                 /* Имя файла базы данных                */
        public static string DatabasePathFile { get; private set; }     /* Полный путь и имя файла базы данных  */

        public static void InitializeDatabase()
        {
            /* Файл базы данных с именем Database будер располагаться в кталоге */
            /* %AppData%\RFServer\. Информация о полном пути и имени файла базы */
            /* данных находится в свойстве DatabasePathFile                     */

            DatabaseName = "default.sdf";
            DatabasePathFile = WorkingDirectory + DatabaseName;

            Log("[database]: Using " + Setup.DatabasePathFile);
        }

        public static bool DropBoxInUse { get; set; }                   /* Взаимодействие с DropBox             */
        private static string DropboxDirectory;                         /* Директория для файлов в DropBox      */
        public static string DropBoxDirectory                           /* DropboxDirectory + "\"               */
        {
            get { return DropboxDirectory; }
            set
            {
                if (value == null)
                    DropboxDirectory = null;
                else if (value == "")
                    DropboxDirectory = "";
                else
                    DropboxDirectory = value + ((value[value.Length - 1] == Path.DirectorySeparatorChar) ? "" : Path.DirectorySeparatorChar.ToString());
            }
        }             
        public static string DropBoxRFSDirectory                        /* DropboxDirectory + ".rfserver\"      */
        {
            get { return DropBoxDirectory + ".rfserver\\"; }
        }
        public static string DropBoxDatabaseName { get; private set; }  /* Имя файла базы данных DropBox        */
        public static string DropBoxDatabasePathFile                    /* Путь и файл базы данных Dropbox      */
        {
            get { return DropBoxRFSDirectory + DropBoxDatabaseName; }
        }

        public static bool InDropBox(string Filename)                   /* true, если используется DropBox и    */
                                                                        /* файл размещен в директории DropBox   */
        {
            if (!DropBoxInUse || DropBoxDirectory == null)
                return false;

            string filename = Filename.ToLower();
            string dropboxpath = Setup.DropBoxDirectory.ToLower();

            return filename.Contains(dropboxpath);
        }

        private static void InitializeDropBox()
        {
            try
            {
                DropBoxDatabaseName = "categories.sdf";

                if (!Directory.Exists(DropBoxRFSDirectory))
                    Directory.CreateDirectory(DropBoxRFSDirectory);
            }
            catch (Exception e)
            {
                Log("[setup]: Cannot create DropBox directory '" + DropBoxRFSDirectory + "'", e);
                DropBoxInUse = false;
            }
        }

        public static bool AutomaticallyRemoveEmptyTags { get; set; }   /* Удаление пустых тэгов автоматически  */

        /* Выгрузка файлов клиентов */

        public static string UploadDir { get; set; }                    /* %APPDATA%\RFServer\Upload            */

        private static void InitializeUpload()
        {
            UploadDir = WorkingDirectory + "Upload\\";

            if (!Directory.Exists(UploadDir))
                Directory.CreateDirectory(UploadDir);
        }

        /* Изменения в файловой системе */

        public static bool Watcher { get; set; }

        /* Лог */

        #region Лог

        private static StreamWriter log;
        public static string LogPathName                                /* %APPDATA%\RFServer\log.txt           */
        {
            get { return WorkingDirectory + "log.txt"; }
        }
        public static bool LogAdvanced { get; set; }
        public static InteractiveConsole ConsoleForm { get; set; }      /* Консоль (диалоговое окно)            */
        public static bool IsService { get; private set; }              /* Запущен ли сервер как служба Windows */

        public static void InitializeLog()
        {
            try
            {
                log = new StreamWriter(LogPathName);
            }
            catch (Exception)
            {
                log = null;
            }
        }

        public static void LogToFile(string text)
        {
            if (log != null)
            {
                lock (log)
                {
                    log.WriteLine(text);
                    log.Flush();
                }
            }
        }

        public static void Log_Advanced(string text)
        {
            if (LogAdvanced)
                Log(text);
        }

        public static void Log(string text, Exception e)
        {
            Log(text + "\n\n" + e.Message + "\n");
        }

        public static void Log(string text)
        {
            LogToFile(text);

            try
            {
                if (ConsoleForm != null)
                    ConsoleForm.BeginInvoke(ConsoleForm.logMethod, text);
            }
            catch (Exception e)
            {
                LogToFile("[setup]: Exception accessing to console:\n\n" + e + "\n");
            }
        }

        #endregion

        /* Виртуальный диск */

        public static bool Drive { get; set; }
        public static bool DriveLetterSpecified { get; private set; }
        public static char DriveLetter { get; set; }

        /* Аргументы командной строки */

        /// <summary>
        /// Функция проверяет правильность аргументов командной строки. Если все арументы
        /// указаны верно, то
        ///  1. Функция указывает, в каком режиме запускается сервер
        ///     (служба, консольное приложение, конфигурация)
        ///  2. Устанавливает все настроечные аргументы, переданные в
        ///     командной строке
        /// </summary>
        /// <param name="arguments">Аргументы командной строки</param>
        /// <returns>
        /// -1      - аргументы командной строки указаны неверно
        ///  0      - запуск как службы
        ///  1      - запуск как консольного приложения
        ///  2      - запуск конфигурации сервера
        ///  3      - добавление нового файла в базу данных
        /// </returns>

        public static int CommandLineArguments(string[] arguments)
        {
            List<string> args = new List<string>(arguments);

            for (int i = 0; i < args.Count; i++)
                args[i] = args[i].ToLower();

            /* Если аргументов нет, значит запускается служба и монтируется */
            /* виртуальный диск (буква указана в реестре)                   */

            if (args.Count == 1)
            {
                /* Запуск службы */

                Drive = true;
                IsService = true;

                return 0;
            }
            else if (args.Contains("-setup"))
            {
                /* Запуск конфигурации сервера */

                IsService = false;

                return 2;
            }
            else if (args.Contains("-insert"))
            {
                /* Добавление нового файла в базу данных */

                IsService = false;

                if (args.Count != 3)
                    return -1;

                return 3;
            }
            else if (args.Contains("-console"))
            {
                /* Запуск консольного приложения */

                IsService = false;

                /* Все остальные аргументы имеют действие только в том случае,  */
                /* если пользователь запускает сервер как консольное приложение */
            }
            else
                return -1;

            /* Следует ли монтировать виртуальный диск? */

            if (args.Contains("-d"))
            {
                Drive = true;

                try
                {
                    int parameter_index = args.IndexOf("-d") + 1;
                    string parameter = args[parameter_index].ToUpper();

                    if (parameter.Length != 1)
                        throw new ArgumentOutOfRangeException("Not a letter");

                    DriveLetter = parameter[0];

                    args.RemoveAt(parameter_index);

                    if (DriveLetter < 'A' || DriveLetter > 'Z')
                        throw new Exception("Drive letter is incorrect");

                    DriveLetterSpecified = true;
                }
                catch (ArgumentOutOfRangeException)
                {
                    DriveLetterSpecified = false;
                }
                catch (Exception)
                {
                    return -1;
                }

                args.Remove("-d");
            }

            /* Какой порт следует назначить серверу? */

            if (args.Contains("-port"))
            {
                try
                {
                    int parameter_index = args.IndexOf("-port") + 1;
                    int port;

                    Int32.TryParse(args[parameter_index], out port);

                    Port = port;

                    Log("[rfserver]: Using port " + Port);

                    args.RemoveAt(parameter_index);
                    args.Remove("-port");
                }
                catch (Exception)
                {
                    return -1;
                }
            }

            /* Если остались еще какие-либо аргументы, кроме имени файла и  */
            /* аргумента -console, значит они заданы неверное               */

            if (args.Count != 2)
                return -1;

            /* Все параметры обработаны */

            return 1;
        }

        /* Инициализация настроек по умолчанию */

        public static void InitializeSettings()
        {
            Port = 2391;
            IP = IPAddress.Any;

            LogAdvanced = false;
            ConsoleForm = null;

            Drive = true;
            DriveLetterSpecified = false;

            DropBoxInUse = false;
            DropBoxDirectory = "";

            Watcher = true;
            AutomaticallyRemoveEmptyTags = true;
        }

        /* Загрузка настроек из файла RFServer.exe.config */

        public static void Load()
        {
            try
            {
                Port = Int32.Parse(ConfigurationManager.AppSettings["Port"]);
                LogAdvanced = Boolean.Parse(ConfigurationManager.AppSettings["LogAdvanced"]);
                Drive = Boolean.Parse(ConfigurationManager.AppSettings["Drive"]);
                DriveLetter = Char.Parse(ConfigurationManager.AppSettings["DriveLetter"]);
                DriveLetterSpecified = true;
                Watcher = Boolean.Parse(ConfigurationManager.AppSettings["Watcher"]);
                DropBoxInUse = Boolean.Parse(ConfigurationManager.AppSettings["DropboxInUse"]);
                DropBoxDirectory = ConfigurationManager.AppSettings["DropboxDirectory"];
                AutomaticallyRemoveEmptyTags = Boolean.Parse(ConfigurationManager.AppSettings["RemoveEmptyTags"]);
            }
            catch (Exception e)
            {
                Setup.Log("[setup]: Cannot read App.config settings (using default)", e);
            }
        }

        /* Сохранение настроек в файл RFServer.exe.config */

        public static void Save()
        {
            try
            {
                Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

                config.AppSettings.Settings["Port"].Value = Port.ToString();
                config.AppSettings.Settings["LogAdvanced"].Value = LogAdvanced.ToString();
                config.AppSettings.Settings["Drive"].Value = Drive.ToString();
                config.AppSettings.Settings["DriveLetter"].Value = DriveLetter.ToString();
                config.AppSettings.Settings["Watcher"].Value = Watcher.ToString();
                config.AppSettings.Settings["DropBoxInUse"].Value = DropBoxInUse.ToString();
                config.AppSettings.Settings["DropBoxDirectory"].Value = DropBoxDirectory.ToString();
                config.AppSettings.Settings["RemoveEmptyTags"].Value = AutomaticallyRemoveEmptyTags.ToString();
                config.Save(ConfigurationSaveMode.Modified);

                ConfigurationManager.RefreshSection("appSettings");
            }
            catch (Exception e)
            {
                Setup.Log("[setup]: Cannot write settings to App.config", e);
            }
        }

        public static void Initialize()
        {
            InitializeLog();

            Log("[setup]: Loading settings...");

            /* Установка значений по умолчанию */

            InitializeDatabase();
            InitializeSettings();
            InitializeUpload();

            /* Загрузка значений из RFServer.exe.config */

            Load();

            /* Продолжение инициализации после загрузки параметров */

            InitializeDropBox();
        }

        public static void Finilize()
        {
            if (log != null)
                log.Close();
        }
    }
}
